package cn.edu.sdjzu.knoverse.service.impl;

import cn.edu.sdjzu.knoverse.service.FileNodeService;
import cn.edu.sdjzu.knoverse.service.RagService;
import cn.edu.sdjzu.knoverse.utils.context.BaseContext;
import cn.edu.sdjzu.knoverse.utils.exception.CustomException;
import cn.edu.sdjzu.knoverse.utils.FileUtil;
import cn.edu.sdjzu.knoverse.utils.minio.MinioProperties;
import cn.edu.sdjzu.knoverse.utils.minio.MinioUtil;
import cn.edu.sdjzu.knoverse.model.dto.system.files.FileUploadDto;
import cn.edu.sdjzu.knoverse.model.dto.system.files.FilesAddDto;
import cn.edu.sdjzu.knoverse.model.dto.system.files.FilesDto;
import cn.edu.sdjzu.knoverse.model.dto.system.files.FilesUpdateDto;
import cn.edu.sdjzu.knoverse.model.entity.system.Files;
import cn.edu.sdjzu.knoverse.model.minio.MinioFilePath;
import cn.edu.sdjzu.knoverse.model.vo.result.PageResult;
import cn.edu.sdjzu.knoverse.model.vo.result.ResultCodeEnum;
import cn.edu.sdjzu.knoverse.model.vo.system.files.FileInfoVo;
import cn.edu.sdjzu.knoverse.model.vo.system.files.FilesVo;
import  cn.edu.sdjzu.knoverse.mapper.FilesMapper;
import  cn.edu.sdjzu.knoverse.service.FilesService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
import dev.langchain4j.data.document.parser.apache.poi.ApachePoiDocumentParser;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 系统文件 服务实现类
 */
@Service
@Transactional
public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements FilesService {

    @Value("${spring.servlet.multipart.max-file-size}")
    private String maxFileSize;

    @Autowired
    private MinioProperties properties;

    @Autowired
    private MinioUtil minioUtil;

    @Autowired
    private FilesMapper filesMapper;

    @Resource
    RagService ragService;

    @Autowired
    private FileNodeService fileNodeService;

    /**
     * * 系统文件表 服务实现类
     *
     * @param pageParams 系统文件表分页查询page对象
     * @param dto        系统文件表分页查询对象
     * @param hasAllPermission 是否有全部权限
     * @param userId     用户ID
     * @return 查询分页系统文件表返回对象
     */
    @Override
    public PageResult<FilesVo> getFilesList(Page<Files> pageParams, FilesDto dto, boolean hasAllPermission, Long userId) {
        // 如果没有files::all权限，则只能查看自己创建的文件
        if (!hasAllPermission) {
            if (dto == null) {
                dto = new FilesDto();
            }
            dto.setCreateUser(userId);
        }
        
        IPage<FilesVo> page = baseMapper.selectListByPage(pageParams, dto);

        return PageResult.<FilesVo>builder()
                .list(page.getRecords())
                .pageNo(page.getCurrent())
                .pageSize(page.getSize())
                .total(page.getTotal())
                .build();
    }

    /**
     * * 获取所有系统文件列表
     *
     * @param hasAllPermission 是否有全部权限
     * @param userId 用户ID
     * @return 系统文件表返回列表
     */
    @Override
    public List<FilesVo> getAllFilesList(boolean hasAllPermission, Long userId) {
        // 如果没有files::all权限，则只能查看自己创建的文件
        FilesDto dto = new FilesDto();
        if (!hasAllPermission) {
            dto.setCreateUser(userId);
        }
        
        // 创建一个不分页的查询，只用于获取全部符合条件的数据
        Page<Files> pageParams = new Page<>();
        pageParams.setSize(-1); // 设置为-1表示不分页，获取所有记录
        
        IPage<FilesVo> page = baseMapper.selectListByPage(pageParams, dto);
        
        return page.getRecords();
    }

    /**
     * * 上传文件
     *
     * @param dto 文件上传
     * @return 管理端返回文件信息
     */
    @SneakyThrows
    @Override
    public FileInfoVo upload(FileUploadDto dto) {
        // 上传的文件
        MultipartFile file = dto.getFile();
        // 上传文件类型
        String type = dto.getType();
        // 管理员Id
        Long userId = BaseContext.getUserId();
        // 文件大小
        long fileSize = file.getSize();
        // 文件类型
        String contentType = file.getContentType();
        // 文件名
        String filename = file.getOriginalFilename();
        // 检查数据是否过大
        String mb = maxFileSize.replace("MB", "");
        if (fileSize / 1024 / 1024 > Long.parseLong(mb)) {
            throw new CustomException(ResultCodeEnum.DATA_TOO_LARGE);
        }
        // 上传文件
        MinioFilePath minioFIlePath = minioUtil.uploadObjectReturnFilePath(file, type);
        String bucketNameFilepath = minioFIlePath.getBucketNameFilepath();

        // 插入文件信息
        Files adminFiles = new Files();
        adminFiles.setFileSize(fileSize);
        adminFiles.setFileType(contentType);
        adminFiles.setFilename(filename);
        adminFiles.setFilepath(bucketNameFilepath);
        adminFiles.setCreateUser(userId);
        
        // 保存到关系数据库
        filesMapper.insert(adminFiles);
        
        // 同步到图数据库
        fileNodeService.saveFileNode(adminFiles.getId());

        // 返回信息内容化
        return FileInfoVo.builder()
                .size(FileUtil.getSize(fileSize))
                .filepath(bucketNameFilepath)
                .fileSize(fileSize)
                .fileType(contentType)
                .filename(filename)
                .url(minioUtil.getObjectNameFullPath(bucketNameFilepath))
                .build();
    }

    /**
     * 删除|批量删除系统文件表
     *
     * @param ids 删除id列表
     * @param hasAllPermission 是否有全部权限
     * @param userId 用户ID
     */
    @Override
    public void deleteFiles(List<Long> ids, boolean hasAllPermission, Long userId) {
        if (ids.isEmpty()) throw new CustomException(ResultCodeEnum.REQUEST_IS_EMPTY);

        // 如果没有files::all权限，检查是否只删除自己创建的文件
        if (!hasAllPermission) {
            // 查询文件所有者
            long count = this.count(Wrappers.<Files>lambdaQuery()
                    .in(Files::getId, ids)
                    .ne(Files::getCreateUser, userId));
            if (count > 0) {
                throw new CustomException(ResultCodeEnum.NO_PERMISSION);
            }
        }

        // 查询文件路径
        List<String> list = list(Wrappers.<Files>lambdaQuery().in(Files::getId, ids)).stream()
                .map(files -> {
                    String filepath = files.getFilepath();
                    int end = filepath.indexOf("/", 1);
                    return filepath.substring(end + 1);
                }).toList();

        // 删除目标文件
        minioUtil.removeObjects(list);
        
        // 批量从图数据库中删除节点
        fileNodeService.batchDeleteFileNodes(ids);
        
        // 批量从关系数据库中删除
        baseMapper.deleteBatchIdsWithPhysics(ids);
    }

    /**
     * 添加系统文件表
     *
     * @param dto 系统文件表添加
     */
    @Override
    public void addFiles(FilesAddDto dto) {
        List<Files> filesList = dto.getFiles().stream().map(file -> {
            try {
                MinioFilePath minioFilePath = minioUtil.uploadObjectReturnFilePath(file, dto.getFilepath());

                Files files = new Files();
                files.setFileType(file.getContentType());
                files.setFileSize(file.getSize());
                files.setFilepath("/" + properties.getBucketName() + minioFilePath.getFilepath());
                files.setFilename(minioFilePath.getFilename());
                return files;
            } catch (IOException e) {
                throw new CustomException(e.getMessage());
            }
        }).toList();
        
        // 批量保存到关系数据库
        saveBatch(filesList);
        
        // 获取保存后的文件ID列表
        List<Long> fileIds = filesList.stream().map(Files::getId).toList();
        
        // 批量同步到图数据库
        fileNodeService.batchSaveFileNodes(fileIds);
    }

    /**
     * 更新系统文件表
     *
     * @param dto 系统文件表更新
     * @param hasAllPermission 是否有全部权限
     * @param userId 用户ID
     */
    @Override
    public void updateFiles(@Valid FilesUpdateDto dto, boolean hasAllPermission, Long userId) {
        Long id = dto.getId();
        Files files = getOne(Wrappers.<Files>lambdaQuery().eq(Files::getId, id));
        
        // 如果没有files::all权限，检查是否是自己创建的文件
        if (!hasAllPermission && !userId.equals(files.getCreateUser())) {
            throw new CustomException(ResultCodeEnum.NO_PERMISSION);
        }
        
        MultipartFile file = dto.getFiles();

        if (file != null) {
            // 文件路径
            String filePath = files.getFilepath().replace("/" + properties.getBucketName() + "/", "");
            minioUtil.updateFile(properties.getBucketName(), filePath, file);

            // 设置文件信息
            files.setFileSize(file.getSize());
            files.setFileType(file.getContentType());
        }

        // 更新内容
        files = new Files();
        BeanUtils.copyProperties(dto, files);
        updateById(files);
    }

    /**
     * * 下载文件
     *
     * @param fileId 文件id
     * @return 文件字节数组
     */
    @Override
    public ResponseEntity<byte[]> downloadFilesByFileId(Long fileId) {
        // 查询数据库文件信息
        Files files = getOne(Wrappers.<Files>lambdaQuery().eq(Files::getId, fileId));

        // 判断文件是否存在
        if (files == null) throw new CustomException(ResultCodeEnum.FILE_NOT_EXIST);

        // 从Minio获取文件
        String filepath = files.getFilepath();
        int end = filepath.indexOf("/", 1);
        filepath = filepath.substring(end + 1);
        byte[] bytes = minioUtil.getBucketObjectByte(filepath);

        // 使用SQL直接更新下载次数，避免并发问题
        filesMapper.increaseDownloadCount(fileId);

        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", files.getFilename());

        return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
    }

    /**
     * * 下载文件
     *
     * @param filepath 文件路径
     * @return 文件字节数组
     */
    @Override
    public ResponseEntity<byte[]> downloadFilesByFilepath(String filepath) {
        // 截取文件路径
        int start = filepath.indexOf("/", 1);
        filepath = filepath.substring(start + 1);
        byte[] bytes = minioUtil.getBucketObjectByte(filepath);

        // 设置文件名称
        int end = filepath.lastIndexOf("/");
        String filename = filepath.substring(end + 1);
        
        // 查找文件记录并使用SQL直接更新下载次数，避免并发问题
        Files files = getOne(Wrappers.<Files>lambdaQuery().eq(Files::getFilepath, "/" + properties.getBucketName() + "/" + filepath));
        if (files != null) {
            filesMapper.increaseDownloadCount(files.getId());
        }

        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", filename);

        return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
    }

    /**
     * * 获取所有文件类型
     *
     * @return 媒体文件类型列表
     */
    @Override
    public Set<String> getAllMediaTypes() {
        Set<String> valueList = new HashSet<>();
        Class<?> mediaTypeClass = MediaType.class;

        try {
            for (Field declaredField : mediaTypeClass.getDeclaredFields()) {
                // 获取字段属性值
                declaredField.setAccessible(true);
                String value = declaredField.get(null).toString();

                if (value.matches("\\w+/.*")) {
                    valueList.add(value);
                }
            }
            return valueList;
        } catch (Exception exception) {
            return Set.of();
        }
    }

    @Override
    public void createFileVectorIndex(Long fileId, boolean hasAllPermission, Long userId) {
        // 获取文件信息
        Files file = getById(fileId);
        if (file == null) throw new CustomException(ResultCodeEnum.FILE_NOT_EXIST);

        // 如果没有files::all权限，检查是否是自己创建的文件
        if (!hasAllPermission && !userId.equals(file.getCreateUser())) {
            throw new CustomException(ResultCodeEnum.NO_PERMISSION);
        }

        // 判断是否是text\docx\pdf\doc
        String fileType = file.getFileType();
        if (!fileType.startsWith("text/") && !fileType.startsWith("application/pdf") && !fileType.startsWith("application/msword") && !fileType.startsWith("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) {
            throw new CustomException(ResultCodeEnum.FILE_TYPE_NOT_SUPPORT);
        }

        // 使用minio下载文件
        String filepath = file.getFilepath();
        int end = filepath.indexOf("/", 1);
        filepath = filepath.substring(end + 1);
        byte[] bytes = minioUtil.getBucketObjectByte(filepath);

        // 根据文件类型选择适当的文档解析器
        DocumentParser documentParser;
        if (fileType.startsWith("application/msword") || fileType.startsWith("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) {
            // 对于Word文档(doc和docx)使用ApachePoiDocumentParser
            documentParser = new ApachePoiDocumentParser();
        } else if (fileType.startsWith("application/pdf")) {
            // 对于PDF文件使用ApachePdfBoxDocumentParser
            documentParser = new ApachePdfBoxDocumentParser();
        } else {
            // 对于其他类型使用ApacheTikaDocumentParser
            documentParser = new ApacheTikaDocumentParser();
        }

        try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
            Document document = documentParser.parse(inputStream);
            String fileContent = document.text();
            ragService.processDocument(fileContent,fileId.toString());
        } catch (Exception e) {
            throw new CustomException(ResultCodeEnum.FILE_PARSE_ERROR);
        }
        // 更新文件状态
        file.setEmbedded((byte) 1);
        updateById(file);
    }
}
